package jbitarrays;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;

import jbitarrays.api.CrawlerReader;
import jbitarrays.api.CrawlerWriter;
import jbitarrays.api.InfiniteBitArray;

/**
 * @see JBitArrays#runLengthEncodingReader(CrawlerReader, int)
 */
@NotThreadSafe final class RunLengthEncodingReader implements CrawlerReader {
	private final int bitsForNumber;
	private final CrawlerReader decorated;

	private boolean readingOnes;
	private int leftToRead;

	public RunLengthEncodingReader(@Nonnull CrawlerReader decorated, @Nonnegative int bitsForNumber) {
		this(decorated, bitsForNumber,/*assume first is one - readNextIfRequired flips*/false, 0);
	}

	private RunLengthEncodingReader(@Nonnull CrawlerReader decorated, @Nonnegative int bitsForNumber, boolean readingOnes, int leftToRead) {
		if (decorated == null || bitsForNumber <= 0 || bitsForNumber > Integer.SIZE) {
			throw new IllegalArgumentException("decorated != null && Integer.SIZE >= bitsForNumber > 0");
		}
		this.bitsForNumber = bitsForNumber;
		this.decorated = decorated;
		this.readingOnes = readingOnes;
		this.leftToRead = leftToRead;
	}

	private void readNextIfRequired() {
		while (leftToRead == 0) {
			leftToRead = decorated.getNextN(bitsForNumber);
			readingOnes = !readingOnes;
		}
	}

	public boolean getNext() {
		readNextIfRequired();
		--leftToRead;
		return readingOnes;
	}

	/**
	 * If you try to read a number from an empty {@link InfiniteBitArray} it
	 * will loop indefinitely - best to throw here to discourage this
	 */
	public CrawlerWriter toWriter() throws UnsupportedOperationException {
		throw new UnsupportedOperationException();
	}

	public int getNextN(int howMany) {
		Utils.checkHowMany(howMany);
		int ret = 0;
		while (howMany > 0) {
			readNextIfRequired();
			int readThisTime = Math.min(leftToRead, howMany);
			ret <<= readThisTime;
			if (readingOnes) {
				ret |= (1 << readThisTime) - 1;
			}
			leftToRead -= readThisTime;
			howMany -= readThisTime;
		}
		return ret;
	}

	@Override public @Nonnull RunLengthEncodingReader clone() {
		return new RunLengthEncodingReader(decorated.clone(), bitsForNumber, readingOnes, leftToRead);
	}
}
